/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.ext.ckez;

import cn.easyplatform.web.ext.Assignable;
import cn.easyplatform.web.ext.Writable;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Strings;
import org.zkoss.util.resource.Labels;
import org.zkoss.web.servlet.http.Encodes;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.WebApp;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.ext.Disable;
import org.zkoss.zk.ui.ext.Readonly;

import java.io.File;
import java.util.Map;

/**
 * The component used to represent <a href="http://ckeditor.com/">CKEditor</a>
 *
 * @author jimmy
 * @version $Revision: 3.0 $ $Date: 2009/10/7 17:56:45 $
 */
public class CKeditor extends HtmlBasedComponent implements Disable, Readonly,
        Assignable, Writable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    /**
     * 显示类型 classic|inline|balloon|document|block
     */
    private String _type;

    private String _value = "";

    private String _width = "100%";

    private String _height = "200px";

    private String _toolbar;

    private Map<String, Object> _config;

    private String _txtByClient;

    private String _hflex;

    private String _vflex;

    private boolean _disabled;

    private String _placeholder;

    private String _filePath;

    private static String fileUploadHandleUrl;

    /**
     * 当target=path时文件名是否混淆
     */
    private boolean _obfuscation;

    static {
        addClientEvent(CKeditor.class, Events.ON_CHANGE, CE_IMPORTANT
                | CE_REPEAT_IGNORE | CE_NON_DEFERRABLE);
        addClientEvent(CKeditor.class, Events.ON_CHANGING, CE_BUSY_IGNORE
                | CE_NON_DEFERRABLE);
    }

    public final static void setFileUploadHandleUrl(String url) {
        fileUploadHandleUrl = url;
    }

    public void setPlaceholder(String placeholder) {
        if (placeholder != null && placeholder.length() == 0)
            placeholder = null;
        if (!Objects.equals(_placeholder, placeholder)) {
            _placeholder = placeholder;
            smartUpdate("placeholder", placeholder);
        }
    }

    public String getPlaceholder() {
        return _placeholder;
    }

    public void setFilePath(String filePath) {
        this._filePath = filePath;
    }

    public String getType() {
        return _type;
    }

    public void setType(String type) {
        if (type != null && type.length() == 0)
            type = null;
        if (!Objects.equals(_type, type)) {
            this._type = type;
            smartUpdate("type", type);
        }
    }

    @Override
    public boolean isDisabled() {
        return _disabled;
    }

    @Override
    public void setDisabled(boolean disabled) {
        if (this._disabled != disabled) {
            this._disabled = disabled;
            smartUpdate("disabled", _disabled);
        }
    }

    @Override
    public boolean isReadonly() {
        return isDisabled();
    }

    @Override
    public void setReadonly(boolean readonly) {
        setDisabled(readonly);
    }

    /**
     * Returns the value in this CKeditor.
     */
    public String getValue() {
        return _value;
    }

    /**
     * Sets the value for this CKeditor.
     */
    public void setValue(Object value) {
        if (value == null)
            value = "";
        if (!value.equals(_value)) {
            _value = (String) value;
            if (_txtByClient == null || !Objects.equals(_txtByClient, value)) {
                _txtByClient = null;
                smartUpdate("value", value);
            }
        }
    }

    /**
     * Returns the toolbar set of this CKeditor.
     * <p>
     * Default: null
     */
    public String getToolbar() {
        return _toolbar;
    }

    /**
     * Sets the toolbar set for this CKeditor.
     */
    public void setToolbar(String toolbar) {
        if (toolbar != null && toolbar.length() == 0)
            toolbar = null;
        if (!Objects.equals(toolbar, _toolbar)) {
            _toolbar = toolbar;
            smartUpdate("toolbar", toolbar);
        }
    }

    /**
     * Returns the width of this CKeditor.
     * <p>
     * Default: 100%
     */
    public String getWidth() {
        return _width;
    }

    /**
     * Sets the width of this CKeditor.
     */
    public void setWidth(String width) {
        if (width != null && width.length() == 0)
            width = null;
        if (!Objects.equals(width, _width)) {
            _width = width;
            smartUpdate("width", width);
        }
    }

    /**
     * Returns the height of this CKeditor.
     * <p>
     * Default: 200px
     */
    public String getHeight() {
        return _height;
    }

    /**
     * Sets the height of this CKeditor.
     */
    public void setHeight(String height) {
        if (height != null && height.length() == 0)
            height = null;
        if (!Objects.equals(height, _height)) {
            _height = height;
            smartUpdate("height", height);
        }
    }

    /**
     * Sets vertical flexibility hint of this component.
     * <p>
     * Number flex indicates how this component's container distributes
     * remaining empty space among its children vertically. Flexible component
     * grow and shrink to fit their given space. Flexible components with larger
     * flex values will be made larger than components with lower flex values,
     * at the ratio determined by all flexible components. The actual flex value
     * is not relevant unless there are other flexible components within the
     * same container. Once the default sizes of components in a container are
     * calculated, the remaining space in the container is divided among the
     * flexible components, according to their flex ratios.
     * </p>
     * <p>
     * Only positive integer allowed for ckeditor hflex value, other value make
     * no effect of it.
     * </p>
     *
     * @param flex the vertical flex hint.
     * @see #setHflex
     * @see #getVflex
     * @since 3.6.0
     */
    public void setVflex(String flex) {
        if (flex != null && flex.length() == 0)
            flex = null;
        if (!Objects.equals(_vflex, flex)) {
            _vflex = flex;
            smartUpdate("vflex", flex);
        }
    }

    /**
     * Return vertical flex hint of this component.
     * <p>
     * Default: null
     *
     * @return vertical flex hint of this component.
     * @see #setVflex
     * @since 3.6.0
     */
    public String getVflex() {
        return _vflex;
    }

    /**
     * Sets horizontal flex hint of this component.
     * <p>
     * Number flex indicates how this component's container distributes
     * remaining empty space among its children horizontally. Flexible component
     * grow and shrink to fit their given space. Flexible components with larger
     * flex values will be made larger than components with lower flex values,
     * at the ratio determined by all flexible components. The actual flex value
     * is not relevant unless there are other flexible components within the
     * same container. Once the default sizes of components in a container are
     * calculated, the remaining space in the container is divided among the
     * flexible components, according to their flex ratios.
     * </p>
     * <p>
     * Only positive integer allowed for ckeditor vflex value, other value make
     * no effect of it.
     * </p>
     *
     * @param flex horizontal flex hint of this component.
     * @see #setVflex
     * @see #getHflex
     * @since 3.6.0
     */
    public void setHflex(String flex) {
        if (flex != null && flex.length() == 0)
            flex = null;
        if (!Objects.equals(_hflex, flex)) {
            _hflex = flex;
            smartUpdate("hflex", flex);
        }
    }

    /**
     * Returns horizontal flex hint of this component.
     * <p>
     * Default: null
     *
     * @return horizontal flex hint of this component.
     * @see #setHflex
     * @since 3.6.0
     */
    public String getHflex() {
        return _hflex;
    }

    public void setFocus() {
        smartUpdate("focus", true);
    }

    /**
     * @return the obfuscation
     */
    public boolean isObfuscation() {
        return _obfuscation;
    }

    /**
     * @param obfuscation the obfuscation to set
     */
    public void setObfuscation(boolean obfuscation) {
        this._obfuscation = obfuscation;
    }

    public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
        final String cmd = request.getCommand();
        if (cmd.equals(Events.ON_CHANGE)) {
            InputEvent evt = InputEvent.getInputEvent(request, _value);
            final String value = evt.getValue();
            _txtByClient = value;
            try {
                final Object oldval = _value;
                setValue(value);
                if (oldval == _value)
                    return;
            } finally {
                _txtByClient = null;
            }
            Events.postEvent(evt);
        } else if (cmd.equals(Events.ON_CHANGING)) {
            Events.postEvent(InputEvent.getInputEvent(request, _value));
        } else
            super.service(request, everError);
    }

    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
            throws java.io.IOException {
        super.renderProperties(renderer);

        render(renderer, "value", _value);
        render(renderer, "type", _type);
        if (!"100%".equals(_width))
            render(renderer, "width", _width);
        if (!"200px".equals(_height))
            render(renderer, "height", _height);

        render(renderer, "config", _config);

        render(renderer, "toolbar", _toolbar);

        render(renderer, "disabled", _disabled);

        if (!Strings.isBlank(_filePath))
            render(renderer, "uploadUrl", fileUploadHandleUrl);

        if (_hflex != null)
            render(renderer, "hflex", _hflex);
        if (_vflex != null)
            render(renderer, "vflex", _vflex);
    }

    // -- Super --//

    /**
     * Not childable.
     */
    public boolean isChildable() {
        return false;
    }

    @Override
    public String write(String fileName, byte[] data) {
        if (cn.easyplatform.lang.Strings.isBlank(_filePath)) {
            throw new RuntimeException(Labels.getLabel("component.property.not.found", new Object[]{"<ckeditor>", "filePath"}));
        } else {
            if (isObfuscation())
                fileName = RandomStringUtils.randomAlphanumeric(12) + "." + FilenameUtils.getExtension(fileName);
            Writable proxy = (Writable) getAttribute("$proxy");
            WebApp webApp = this.getDesktop().getWebApp();
            if (proxy != null)
                return webApp.getServletContext() + proxy.write(fileName, data);
            else {
                if (_filePath.startsWith("$7"))
                    throw new RuntimeException("Permission denied");
                try {
                    String file = FilenameUtils.normalize(webApp.getRealPath(_filePath)
                            + "/" + fileName);
                    FileUtils.writeByteArrayToFile(new File(file),
                            data);
                    return webApp.getServletContext().getContextPath() + _filePath + "/" + Encodes.encodeURI(fileName);
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        }
    }

    @Override
    public String getFilePath() {
        return _filePath;
    }
}
